!(function (t, e) {
  'object' == typeof exports && 'undefined' != typeof module
    ? (module.exports = e())
    : 'function' == typeof define && define.amd
    ? define(e)
    : ((t = 'undefined' != typeof globalThis ? globalThis : t || self).vio = e())
})(this, function () {
  return {
    isNum(val) {
      return this.getType(val) === 'Number' && !Number.isNaN(val)
    },
    
    getEl(el) {
      return typeof el === 'string' ?
        document.querySelector(el) :
        el
    },
     
    // 随机数组项
    randItem(arr) {
      return this.shuffle(arr)[0]
    },
     
    // 随机数字
    // min max, or length && length <= 20
    // randNum(1, 5)  randNum(8)
    randNum(...args) {
      let limit = 10 ** 20
      if (!args.length) throw new Error('params cannot be empty')
      if (args < 0 || args[0] < 0) throw new Error('params should positive integer')
      if (10 ** args > limit) throw new Error('Exceed maximum limit')
      let min = args.length === 1 ? 10 ** (args - 1) : args[0]
      let max = args.length === 1 ? 10 ** args - 1 : args[1]
      return Math.floor((Math.random() * (max - min + 1)) + min)
    },
     
    // 随机字符串  abbr: u,l,n(u:upper l:lower m:number)
    // randStr(10, 'nl')  randStr(10, 'u')
    randStr(len, abbr = 'uln') {
      if (!/^[uln]{1,3}$/.test(abbr)) {
        throw new Error('params must be u,l,n')
      }
      const n = '0123456789'
      const s = 'abcdefghijklmnopqrstuvwxyz'
      let char = ''
      if (abbr.includes('u')) {
        char += s.toUpperCase()
      }
      if (abbr.includes('l')) {
        char += s
      }
      if (abbr.includes('n')) {
        char += n
      }
      
      let resStr = ''
      for (let i = 0; i < len; i++) {
        resStr += char.charAt(Math.floor(Math.random() * char.length))
      }
      return resStr
    },
     
    // 随机数组 (洗牌算法, 不会改变原数组)
    shuffle(arr) {
      arr = arr.slice()
      let i = arr.length
      while (i) {
        let j = Math.floor(Math.random() * i--)
        ;[arr[j], arr[i]] = [arr[i], arr[j]]
      }
      return arr
    },
     
    // 随机数组
    randArr(arr) {
      return this.shuffle(arr)
    },
     
    // 随机颜色
    randColor() {
      let str = '00000' + ((Math.random() * 16777215 + 0.5) >> 0).toString(16)
      return '#' + str.slice(-6)
    },
     
    // 随机中文姓名
    randCname() {
      const first = '赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张'
      const last = '永勇全真修杰恺学贤超铭琪睿锐瑞卓美彩伟梓庆诚尧瑜瑾雄鸿智熙正阳哲玄博辰俊羽星昊良景康'
      return this.randItem([...first]) + this.randItem([...last]) + this.randItem([...last])
    },
     
    // 数组对象去重
    uniqArr(arr, p = '') {
      if (!p) return [...new Set(arr)]
      let s = new Set()
      return arr.filter(v => !s.has(v[p]) ? s.add(v[p]) && true : false)
    },
     
    // 节流
    throttle(func, wait) {
      let start = 0
      return function(...args) {
        let now = Date.now()
        if (now - start >= wait) {
          func.apply(this, args)
          start = now
        }
      }
    },
     
    // 防抖
    debounce(func, delay) {
      let timer = null
      return function(...args) {
        timer && clearTimeout(timer)
        timer = setTimeout(() => {
          func.apply(this, args)
        }, delay)
      }
    },
    
    // 设置本地存储, ttl单位默认:秒  setStore(key, value, 5)
    // 或ttl: 30s, 3m, 5h, 2d, 1.5d
    setStore(key, val, ttl = '1e12') {
      const now = Math.trunc(Date.now() / 1000)
      if (/[\.e]?\d+(s|m|h|d)$/.test(ttl)) {
        const q = {
          s: 1,
          m: 60,
          h: 3600,
          d: 86400
        }
        const fk = ttl.slice(-1)
        const n = Number(ttl.slice(0, -1))
        ttl = Math.floor(n * q[fk])
      }
      localStorage.setItem(key, JSON.stringify({
        _ttlz: ttl > now ? ttl : ttl + now,
        value: val,
      }))
    },
    
    getStore(key) {
      const lsv = localStorage.getItem(key)
      if (!lsv) return lsv
      try {
        let o = JSON.parse(lsv)
        if (typeof o !== 'object') return lsv
        if (!o._ttlz) return o
        if (o._ttlz - Math.trunc(Date.now() / 1000) > 0) {
          return o.value
        }
        localStorage.removeItem(key)
        return null
      } catch (e) {
        return lsv
      }
    },
    
    setSessionStore(key, val) {
      const sVal = typeof val === 'object' ? JSON.stringify(val) : val
      sessionStorage.setItem(key, sVal)
    },
    
    getSessionStore(key) {
      const sVal = sessionStorage.getItem(key)
      if (/^[\{\[]/.test(sVal)) {
        try {
          return JSON.parse(sVal)
        } catch (e) {
          return sVal
        }
      }
      return sVal
    },
     
    // 四舍五入保留小数后n位
    round(val, n = 2) {
      return Number(`${Math.round(`${String(val)}e${n}`)}e-${n}`)
    },
     
    // 四舍五入保留小数后n位, 位数不足尾部填充零
    roundFO(val, n = 2) {
      return (this.round(val, n)).toFixed(n)
    },
     
    // 向下取整保留小数后n位
    floor(val, n = 2) {
      return Number(`${Math.floor(`${String(val)}e${n}`)}e-${n}`)
    },
    // 向下取整保留小数后n位, 位数不足尾部填充零
    floorFO(val, n = 2) {
      return (this.floor(val, n)).toFixed(n)
    },
    
    // 向上取整保留小数后n位
    ceil(val, n = 2) {
      return Number(`${Math.ceil(`${String(val)}e${n}`)}e-${n}`)
    },
    // 向上取整保留小数后n位, 位数不足尾部填充零
    ceilFO(val, n = 2) {
      return (this.ceil(val, n)).toFixed(n)
    },
     
    // 延时器
    timeout(ms) {
      return new Promise(resolve => setTimeout(resolve, ms))
    },
     
    // 复制文本(已废弃) 将来可能会移除
    copyTextOld(text) {
      const input = document.createElement('input')
      input.setAttribute('readonly', 'readonly')
      input.setAttribute('value', text)
      document.body.appendChild(input)
      input.select()
      document.execCommand('copy')
      document.body.removeChild(input)
    },
     
    // 复制文本(推荐)
    async copyText(str) {
      return navigator.clipboard.writeText(str)
    },
     
    // 下载文本
    downloadText(content, filename) {
      let el = document.createElement('a')
      el.download = filename
      el.style.display = 'none'
      let blob = new Blob([content])
      el.href = URL.createObjectURL(blob)
      document.body.appendChild(el)
      el.click()
      document.body.removeChild(el)
    },
     
    // 格式化时间
    fmt(date, fstr = 'Y-M-D h:m:s') {
      if (!date) return ''
      if (typeof date === 'string' && !/T\d{2}:/.test(date)) {
        date = date.replace(/-/g, '/')
      }
      const t = new Date(date)
      const o = {
        Y: t.getFullYear(),
        M: t.getMonth() + 1,
        D: t.getDate(),
        h: t.getHours(),
        m: t.getMinutes(),
        s: t.getSeconds(),
      }
      return fstr.replace(/Y|M|D|h|m|s/g, m => (o[m]+'').padStart(2, '0'))
    },
     
    // 金额千分位逗号隔开
    toThousands(num) {
      return (num+'').replace(/\B(?=(\d{3})+\b)/g, ',')
    },
     
    // 深拷贝, 深克隆
    deepClone(obj, hash = new WeakMap()) {
      if (obj === null) return obj
      if (obj instanceof Date) return new Date(obj)
      if (obj instanceof RegExp) return new RegExp(obj)
      if (typeof obj !== 'object') return obj
      if (hash.get(obj)) return hash.get(obj)
      let cloneObj = new obj.constructor()
      hash.set(obj, cloneObj)
      for (let key in obj) {
        if (this.has(obj, key)) {
          cloneObj[key] = this.deepClone(obj[key], hash)
        }
      }
      return cloneObj
    },
    
    // 把数字格式化成易读的中文形式
    fmtNumToCN(num = 0, n = 2) {
      const arr = ['亿亿', '万亿', '亿', '万']
      const absNum = Math.abs(num)
      for (let [i, v] of arr.entries()) {
        let c10 = 10 ** (16 - i * 4)
        if (absNum >= c10) {
          return Number(`${Math.floor(`${absNum / c10}e${n}`)}e-${n}`)
            * (num < 0 ? -1 : 1) + v
        }
      }
      return num
    },
    
    // 图片转换成base64编码 @return { Promise }
    imgToBase64(imgUrl) {
      let canvas = document.createElement('canvas')
      let ctx = canvas.getContext('2d')
      let img = new Image
      img.crossOrigin = 'Anonymous'
      img.src = imgUrl
      return new Promise((resolve, reject) => {
        img.addEventListener('load', () => {
          ctx.width = img.width
          ctx.height = img.height
          ctx.drawImage(img, 0, 0, img.width, img.height)
          return resolve(canvas.toDataURL('image/png'))
        }, false)
      })
    },
     
    // 判断数组是否有重复的子项
    isUnique(arr) {
      return arr.length === new Set(arr).size
    },
     
    // 将数组块分成指定大小的更小数组, n个一组
    chunk(arr, size) {
      return Array.from({ length: Math.ceil(arr.length / size) }, (v, i) => {
        return arr.slice(i * size, i * size + size)
      })
    },
    
    // 将一个数组分割成n个更小的数组, 指定长度n
    chunkIntoN(arr, n) {
      const size = Math.ceil(arr.length / n)
      return Array.from({ length: n }, (v, i) => {
        return arr.slice(i * size, i * size + size)
      })
    },
    
    // 获取数据类型 String, Date, Boolean, Array, Number, Function...
    getType(val) {
      return Object.prototype.toString.call(val).match(/\w+(?=\])/)[0]
    },
     
    // 安卓手机震动
    vibrate(val = 50) {
      try {
        globalThis.navigator.vibrate(Array.isArray(val) ? val : [val])
      } catch(e) {}
    },
   
    // 求两个数组的交集并过滤重复的值
    intersection(a, b) {
      const s = new Set(b)
      return [...new Set(a)].filter(x => s.has(x))
    },
     
    // 比较两个对象, 判断对象1是否包含对象2的所有属性和值
    matches(obj, source) {
      return Object.keys(source).every(key => {
        return this.has(obj, key) && obj[key] === source[key]
      })
    },
     
    // js判断是否滚动到页面底部
    isBottom() {
      const el = document.documentElement.scrollTop ? document.documentElement : document.body
      return el.scrollTop + globalThis.innerHeight >= el.scrollHeight
    },
    
    /**
     * @example
     * 例1: scrollTo('.d1', 0) 元素.d1滚动到顶部
     * 例2: scrollTo('.d1', 0, 400) 元素.d1滚动到顶部耗时400毫秒
     * 例3: scrollTo(0) window滚动到顶部
     * 例4: scrollTo(0, 600) window滚动到顶部,耗时600毫秒
    */
    scrollTo(el, yTop, duration = 260) {
      const now = Date.now()
      if (typeof el === 'string') {
        el = this.getEl(el)
      }
      
      if (this.isNum(el)) {
        duration = typeof yTop !== 'undefined' ? yTop : duration
        yTop = el
        el = document.documentElement.scrollTop
          ? document.documentElement
          : document.body
      }
      
      const cdf = el.scrollHeight - el.clientHeight
      yTop = Math.min(cdf, Math.max(0, yTop))
      const rTop = Math.abs(yTop - cdf) < 1 ? cdf : yTop
      const diff = yTop - el.scrollTop
      const iFn = () => {
        const sTop = el.scrollTop
        if (Math.abs(sTop - rTop) > 1) {
          const zNum = sTop + diff / (duration / 16.6)
          const ky = diff > 0 ? zNum > rTop : zNum < rTop
          el.scrollTo(0, ky ? rTop : zNum)
          if (Date.now() - now < duration) {
            requestAnimationFrame(iFn)
          }
        }
      }
      iFn()
    },
     
    // 通过v获取k  value get key
    getKeyByVal(obj, val) {
      if (this.getType(obj) !== 'Object') {
        throw new Error('obj must be an object')
      }
      return Object.keys(obj).find(v => obj[v] === val)
    },
     
    // 替代eval函数
    evalFunc(fnStr, obj = {}) {
      return Function(`return (R) => {${fnStr}}`)(obj)(obj)
    },
     
    isMobile() {
      return (/(iPhone|iPod|iPad|Android|ios|Mobile)/i.test(navigator.userAgent))
    },
    
    isAndroid() {
      return (/(Android|Adr)/i.test(navigator.userAgent))
    },
    
    isIOS() {
      return (/\(i[^;]+;( U;)? CPU.+Mac OS X/.test(navigator.userAgent))
    },
    
    isWx() {
      return (/micromessenger/i.test(navigator.userAgent))
    },
     
    // hexToRgb('#fff')
    // hexToRgb('#f3558392')
    // hexToRgb('f35582')
    hexToRgb(hex) {
      let alpha = false,
        h = hex.slice(hex.startsWith('#') ? 1 : 0)
      if (h.length === 3) {
        h = [...h].map(x => x + x).join('')
      }
      else if (h.length === 8) {
        alpha = true
      }
      h = Number.parseInt(h, 16)
      return (
        'rgb' +
        (alpha ? 'a' : '') +
        '(' +
        (h >>> (alpha ? 24 : 16)) +
        ', ' +
        ((h & (alpha ? 0x00ff0000 : 0x00ff00)) >>> (alpha ? 16 : 8)) +
        ', ' +
        ((h & (alpha ? 0x0000ff00 : 0x0000ff)) >>> (alpha ? 8 : 0)) +
        (alpha ? `, ${h & 0x000000ff}` : '') +
        ')'
      )
    },
     
    // rgb转16进制颜色
    rgbToHex(color) {
      let rgb = color.split(',')
      let r = Number.parseInt(rgb[0].split('(')[1], 10)
      let g = Number.parseInt(rgb[1], 10)
      let b = Number.parseInt(rgb[2].split(')')[0], 10)
      let hex = '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)
      return hex
    },
     
    // 判断是否是二代身份证
    isIdCard(val) {
      return /^\d{6}(18|19|20|21)\d{2}(0\d|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$/.test(val)
    },
    
    // 通过身份证获取出生日期
    getBirthById(id) {
      let str = String(id)
      if (str.length !== 18) {
        throw new Error('必须为二代身份证')
      }
      return str.slice(6, 14).replace(/^(\d{4})(\d{2})(\d{2})$/, '$1-$2-$3')
    },
     
    isIpv4(str) {
      return /^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$/.test(str)
    },
     
    // 设置概率
    prob(prob = 0.5) {
      return prob > Math.random()
    },
    
    // 前端生成uuid
    uuid(isUpper) {
      const id = ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => {
        return (c ^ (self.crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
      })
      return isUpper ? id.toUpperCase() : id
    },
    
    // 获取URL参数
    urlParams(name, query = location.search) {
      return new URLSearchParams(query).get(name)
    },
    
    // 随机字符串
    rndS(len, abbr = 'uln') {
      return this.randStr(len, abbr)
    },
    
    // 随机数字
    rndN(...args) {
      return this.randNum(...args)
    },
    
    // 返回日期在一年中的第几天
    dayOfYear(date) {
      date = new Date(date)
      return Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 86400e3)
    },
    
    // 延时调用函数, 支持次数限制
    deferCall(func, delay, limit, cb) {
      func._x8ct = func._x8ct || 0
      if (++func._x8ct <= limit) {
        setTimeout(func, delay)
      } else {
        cb && cb()
      }
    },
    
    // 从给定的文本中去除html
    stripHtml(html) {
      return new DOMParser().parseFromString(html, 'text/html').body.textContent || ''
    },
    
    // 判断对象是否深度相等
    equals(a, b) {
      if (a === b) return true
      if (a instanceof Date && b instanceof Date) {
        return a.getTime() === b.getTime()
      }
      if (!a || !b || (typeof a !== 'object' && typeof b !== 'object')) {
        return a === b
      }
      if (a.prototype !== b.prototype) return false
      const keys = Object.keys(a)
      if (keys.length !== Object.keys(b).length) return false
      return keys.every(k => this.equals(a[k], b[k]))
    },
    
    once(fn, ...args) {
      const fnp = new Error().stack.toString().replace(/Error.+?\s+at\s.+?at\s(\S+)\s.+/s, '$1') + '_p'
      if (!this.once[fnp]) {
        this.once[fnp] = true
        fn.apply(null, args)
      }
    },
    
    watchDom(el, ...args) {
      el = this.getEl(el)
      let opt = args.length > 1 ? args[0] : {}
      let cb = args.length > 1 ? args[1] : args[0]
      return new MutationObserver((mutations, observer) => {
        cb(mutations)
      }).observe(el, Object.assign({
        childList: true,
        attributes: true,
        characterData: true,
        subtree: true,
      }, opt))
    },
    
    has(obj, key) {
      return Object.prototype.hasOwnProperty.call(obj, key)
    },
    
    fmtBytes(bytes, decimals) {
      const arr = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB']
      let str = ''
      for (let i = arr.length - 1; i >= 0; i--) {
        if (bytes > 1024 ** i || i === 0) {
          str = this.floor(bytes / 1024 ** i, decimals) + arr[i]
          break
        }
      }
      return str
    },
    
    // 随机字符串(0-9a-f)
    gid(len = 40) {
      let res = ''
      while (res.length < len) {
        res += Math.random().toString(16).slice(2, 10)
      }
      return res.slice(-len)
    },
    
    // 仅限https value:Number|String
    async sha256(value) {
      const tbf = new TextEncoder().encode(value)
      const hashBuffer = await crypto.subtle.digest('SHA-256', tbf)
      const hArr = Array.from(new Uint8Array(hashBuffer))
      return hArr.map((b) => b.toString(16).padStart(2, '0')).join('')
    },
    
    sleep(ms) {
      return this.timeout(ms)
    },
    
    isEqual(a, b) {
      return this.equals(a, b)
    },
    
    isEmpty(val) {
      if (val === null || val === void 0)
        return true
      if (typeof val === 'string' || Array.isArray(val))
        return val.length === 0
      if (val instanceof Map || val instanceof Set)
        return val.size === 0
      if (ArrayBuffer.isView(val))
        return val.byteLength === 0
      if (typeof val === 'object')
        return Object.keys(val).length === 0
      return false
    },
    
    isIterable(obj) {
      try {
        return typeof obj[Symbol.iterator] === 'function'
      } catch (e) {
        return false
      }
    },
    
    // {a:1,b:2} --> [{name:'a',value:1},{name:'b',value:2}]
    objToArr(obj, key = 'name', val = 'value') {
      return Object.entries(obj).map(([k, v]) => ({[key]: k, [val]: v}))
    },
    
    // ([{a:'name', b:'val'}], 'a', 'b') --> {name:val}
    arrToObj(arr, k, v) {
      return arr.reduce((p, c, i) => {
        const rk = typeof c[k] !== 'undefined' ? c[k] : i
        const rv = typeof c[v] !== 'undefined' ? c[v] : c
        p[rk] = rv
        return p
      }, {})
    },
    
    group(arr, getKey) {
      if (Object.groupBy) {
        return Object.groupBy(arr, getKey)
      }
      const res = {}
      for (const elem of arr) {
        const key = getKey(elem)
        if (typeof res[key] === 'undefined') {
          res[key] = []
        }
        res[key].push(elem)
      }
      return res
    },
    
    count(arr, criteria) {
      const res = {}
      for (const value of arr) {
        const key = criteria(value)
        if (typeof res[key] === 'undefined') {
          res[key] = 0
        }
        res[key]++
      }
      return res
    },
    
  }
})